package vapu.examples.ddelay;

import java.util.Arrays;

import im.composer.audio.engine.Source;
import im.composer.vapu.VAPU;
import jass.engine.BufferNotAvailableException;

import org.jaudiolibs.audioservers.AudioConfiguration;
import org.tritonus.share.sampled.FloatSampleBuffer;

import themidibus.listener.SimpleMidiListener;

public class DDelay extends VAPU implements SimpleMidiListener {

	public static int P_DELAY_TIME = 0;
	public static int P_FEEDBACK = 1;
	public static int P_LFO_FREQUENCY = 2;
	public static int P_LFO_DEPTH = 3;
	public static int P_WET_DRY_MIX = 4;

	public static int NUM_PARAMS = P_WET_DRY_MIX + 1;

	public static String[] PARAM_NAMES = new String[] { "Delay Time", "Feedback", "LFO Frq", "LFO Depth", "Wet/Dry Mix" };

	public static String[] PARAM_LABELS = new String[] { "ms", "%", "Hz", "", "%" };

	public static float[] PARAM_PRINT_MUL = new float[] { 1000, 100, 1, 1, 100 };

	// Some default programs
	private float[][] programs = new float[][] { { 0.45f, 0.50f, 0.0f, 0f, 0.5f }, { 0.01f, 0.85f, 0.2f, 0.5f, 0.65f }, { 0.99f, 0.7f, 0.0f, 0.02f, 0.50f }, { 0.3f, 0.9f, 0.0f, 0.0f, 0.50f },
			{ 0.004f, 0.80f, 0.1f, 0.8f, 0.50f }, { 0.4f, 0.50f, 0.1f, 0.5f, 0.50f }, { 0.1f, 0.50f, 0.1f, 0.6f, 0.50f }, { 0.1f, 0.50f, 0.1f, 0.7f, 0.50f } };

	private float[][] echo;
	private int echoSize = 0;
	private int echoPos = 0;
	private long echoLFODiff = 0;
	private long echoLFODiffMax = 0;
	private float echoLFODepth = 0.8f;
	private float echoFeedback = 0;
	private float echoLFOSpeed = 0;
	private float echoLFOPos = 0;
	private float echoDW = 0.8f;
	private float sampleRate = 44100;

	private int currentProgram = 0;

	@Override
	public synchronized void configure(AudioConfiguration context) throws Exception {
		super.configure(context);
		sampleRate = context.getSampleRate();
		update();
	}

	// Allocate new buffer when modifying buffer size!
	private void setEchoTime(int channels, float millisDelay) {
		int sampleSize = (int) (millisDelay * sampleRate / 1000);
		echoSize = sampleSize;
		if (echo == null || echo.length != echoSize) {
			echo = new float[channels][echoSize];
		}
	}

	private void update() {
		int nChannels = Math.min(getContext().getInputChannelCount(), getContext().getOutputChannelCount());
		setEchoTime(nChannels, programs[currentProgram][P_DELAY_TIME] * 1000);
		echoFeedback = programs[currentProgram][P_FEEDBACK];
		// Speed of 1 Hz => 2xPI for one revolution!
		// But - depending on number of samples this will be differently large
		// chunks of updates!?
		echoLFOSpeed = (float) (programs[currentProgram][P_LFO_FREQUENCY] * 2 * 3.1415 / sampleRate);
		echoLFODepth = programs[currentProgram][P_LFO_DEPTH];
		echoLFODiffMax = (long) ((echoSize / 2.0) * echoLFODepth);
		echoLFODiff = 0;
		echoDW = programs[currentProgram][P_WET_DRY_MIX];
		echoPos = 0;
	}

	public String getProductString() {
		return "DDelay";
	}

	public String getEffectName() {
		return "DDelay";
	}

	public String getProgramNameIndexed(int category, int index) {
		return "program: cat: " + category + ", " + index;
	}

	public int getNumParams() {
		return NUM_PARAMS;
	}

	public int getNumPrograms() {
		return programs.length;
	}

	public float getParameter(int index) {
		if (index < programs[currentProgram].length)
			return programs[currentProgram][index];
		return 0.0f;
	}

	public String getParameterDisplay(int index) {
		if (index < programs[currentProgram].length) {
			return "" + ((int) (100 * PARAM_PRINT_MUL[index] * programs[currentProgram][index])) / 100.0;
		}
		return "0.0";
	}

	public String getParameterLabel(int index) {
		if (index < PARAM_LABELS.length)
			return PARAM_LABELS[index];
		return "";
	}

	public String getParameterName(int index) {
		if (index < PARAM_NAMES.length)
			return PARAM_NAMES[index];
		return "param: " + index;
	}

	public int getProgram() {
		return currentProgram;
	}

	public String getProgramName() {
		return "program " + currentProgram;
	}

	// Generate / Process the sound!
	public void processBuffer(int channel, float[] inBuffer, float[] outBuffer, int sampleFrames) {
		for (int i = 0, n = sampleFrames; i < n; i++) {
			float exVal = inBuffer[i];
			int echoRead = (int) (echoPos + echoLFODiff);

			if (echoRead >= echoSize) {
				echoRead -= echoSize;
			}

			float out = (exVal * (1.0f - echoDW) + echo[channel][echoRead] * echoDW);
			outBuffer[i] = out;

			exVal = exVal + echo[channel][echoRead] * echoFeedback;

			echo[channel][echoPos] = exVal;
			echoPos = (echoPos + 1);
			if (echoPos >= echoSize)
				echoPos = 0;
		}
		// Update LFO - which is a sine!
		echoLFODiff = (int) (echoLFODiffMax * (1.0 + Math.sin(echoLFOPos)));
		echoLFOPos += echoLFOSpeed * sampleFrames;
	}

	@Override
	protected void executeCommand(long timeStamp, String command) {

	}

	@Override
	protected void computeBuffer() {
		int nChannels = Math.min(getContext().getInputChannelCount(), getContext().getOutputChannelCount());
		FloatSampleBuffer input = new FloatSampleBuffer(nChannels, getContext().getMaxBufferSize(), getContext().getSampleRate());
		for (Source src : getSources()) {
			try {
				input.mix(src.getBuffer());
			} catch (BufferNotAvailableException e) {
			}
		}
		for (int i = 0; i < nChannels; i++) {
			float[] frames = input.getChannel(i);
			processBuffer(i, frames, frames, getContext().getMaxBufferSize());
			buf.setRawChannel(i, frames);
		}
	}

	@Override
	public void activeSensing(long timeStamp) {

	}

	@Override
	public void noteOn(int channel, int pitch, int velocity) {

	}

	@Override
	public void noteOff(int channel, int pitch, int velocity) {

	}

	@Override
	public void controllerChange(int channel, int number, int value) {
		programs[currentProgram][number] = value;
		update();
	}

	@Override
	public void programChange(int channel, int value) {
		currentProgram = value;
		update();
	}

	@Override
	public void pitchBend(int channel, float val) {

	}

	@Override
	public void channelPressure(int channel, float val) {

	}

	@Override
	public void channelVolume(int channel, float val) {

	}

	@Override
	public void sustainChange(boolean b) {

	}
}
